#include "MainBus.h"
#include <cstring>
#include "Log.h"

PNE_NAMESPACE_BEGIN

MainBus::MainBus() : m_RAM(0x800, 0), m_mapper(nullptr)
{
}

Byte MainBus::Read(Address addr)
{
    if (addr < 0x2000)
        return m_RAM[addr & 0x7fff];
    else if (addr < 0x4020)
    {
        if (addr < 0x4000) // PPU registers, mirrored
        {
            auto it = m_readCallbacks.find(static_cast<IORegisters>(addr & 0x2007));
            if (it != m_readCallbacks.end())
                return (it->second)();
            else
                LOG(Level::InfoVerbose) << "No read callback registered for I/O register at: " << std::hex
                                        << +addr << std::endl;
        }
        else if (addr < 0x4018 && addr >= 0x4014)
        {
            auto it = m_readCallbacks.find(static_cast<IORegisters>(addr));
            if (it != m_readCallbacks.end())
                return (it->second)();
            else
                LOG(Level::InfoVerbose) << "No read callback registered for I/O register at: " << std::hex << +addr << std::endl;
        }
        else
            LOG(Level ::InfoVerbose) << "Read access attempt at: " << std::hex << +addr << std::endl;
    }
    else if (addr < 0x6000)
    {
        LOG(Level::InfoVerbose) << "Expansion ROM read attempted. This is currently unsupported" << std::endl;
    }
    else if (addr < 0x8000)
    {
        if (m_mapper->hasExtendedRAM())
        {
            return m_extRAM[addr - 0x6000];
        }
    }
    else
    {
        return m_mapper->ReadPRG(addr);
    }

    return 0;
}

void MainBus::Write(Address addr, Byte value)
{
    if (addr < 0x2000)
        m_RAM[addr & 0x7ff] = value;
    else if (addr < 0x4020)
    {
        if (addr < 0x4000)
        {
            auto it = m_writeCallbacks.find(
                static_cast<IORegisters>(addr & 0x2007));

            if (it != m_writeCallbacks.end())
                (it->second)(value);
            else
                LOG(Level::InfoVerbose) << "No write callback registered for I/O register at: " << std::hex << +addr << std::endl;
        }
        else if (addr < 0x4017 && addr >= 0x4014)
        {
            auto it = m_writeCallbacks.find(
                static_cast<IORegisters>(addr));

            if (it != m_writeCallbacks.end())
                (it->second)(value);
            else
                LOG(Level::InfoVerbose) << "No write callback registered for I/O register at: " << std::hex << +addr << std::endl;
        }
        else
            LOG(Level::InfoVerbose) << "Write access attempt at: " << std::hex << +addr << std::endl;
    }
    else if (addr < 0x6000)
    {
        LOG(Level::InfoVerbose) << "Expansion ROM access attempted. This is cureently unsupported" << std::endl;
    }
    else if (addr < 0x8000)
    {
        if (m_mapper->hasExtendedRAM())
        {
            m_extRAM[addr - 0x6000] = value;
        }
    }
    else
    {
        m_mapper->WritePRG(addr, value);
    }
}

const Byte *MainBus::GetPagePtr(Byte page)
{
    Address addr = page << 8;
    if (addr < 0x2000)
    {
        return &m_RAM[addr & 0x7ff];
    }
    else if (addr < 0x4020)
    {
        LOG(Level::InfoVerbose) << "Register address memory pointer access attempt" << std::endl;
    }
    else if (addr < 0x6000)
    {
        LOG(Level::Error) << "Expansion ROM access attempted, which is unsupported" << std::endl;
    }
    else if (addr < 0x8000)
    {
        if (m_mapper->hasExtendedRAM())
        {
            return &m_extRAM[addr - 0x6000];
        }
    }
    else
    {
        LOG(Level::Error) << "Unexpected DMA request: " << std::hex
                          << "0x" << +addr << " (" << +page << ")" << std::dec << std::endl;
    }

    return nullptr;
}

bool MainBus::SetMapper(Mapper *mapper)
{
    m_mapper = mapper;
    if (!mapper)
    {
        LOG(Level::Error) << "Mapper pointer is nullptr" << std::endl;
        return false;
    }

    if (mapper->hasExtendedRAM())
    {
        m_extRAM.resize(0x2000);
    }

    return true;
}

bool MainBus::SetWriteCallback(IORegisters reg,
                               std::function<void(Byte)> callback)
{
    if (!callback)
    {
        LOG(Level::Error) << "callback argument is nullptr" << std::endl;
        return false;
    }
    return m_writeCallbacks.emplace(reg, callback).second;
}

bool MainBus::SetReadCallback(
    IORegisters reg,
    std::function<Byte(void)> callback)
{
    if (!callback)
    {
        LOG(Level::Error) << "callback argument is nullptr" << std::endl;
    }

    return m_readCallbacks.emplace(reg, callback).second;
}

PNE_NAMESPACE_END
// DONE